/*
 * Lenovo Group
 * Copyright (c) 2015-2016 All Rights Reserved.
 * 
 * Project Name:itsm-gateway
 * Create Time: 2016年1月30日 下午3:02:56
 */
package com.lenovo.lmrp.server.itsmgateway.adapter.ms;

import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import javax.xml.ws.WebServiceContext;
import javax.xml.ws.handler.MessageContext;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.core.JsonParser.Feature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.XmlFactory;
import com.lenovo.lmrp.api.constant.BizErrorCode;
import com.lenovo.lmrp.api.constant.TicketAction;
import com.lenovo.lmrp.api.model.entity.AddressPO;
import com.lenovo.lmrp.api.model.entity.AttachmentPO;
import com.lenovo.lmrp.api.model.entity.AttachmentPO.AttachmentStatus;
import com.lenovo.lmrp.api.model.entity.EntityMetadataPO;
import com.lenovo.lmrp.api.model.entity.ProjectEngineerPO;
import com.lenovo.lmrp.api.model.entity.ProjectPO;
import com.lenovo.lmrp.api.model.entity.TicketEventPO;
import com.lenovo.lmrp.api.model.entity.TicketPO;
import com.lenovo.lmrp.api.model.entity.TicketSLAPO;
import com.lenovo.lmrp.api.model.entity.TicketSLAPO.TicketSLAStep;
import com.lenovo.lmrp.api.model.view.ExceptionEventContent;
import com.lenovo.lmrp.base.IFileService;
import com.lenovo.lmrp.base.LowerCaseMap;
import com.lenovo.lmrp.base.event.EventCode;
import com.lenovo.lmrp.base.event.EventContent;
import com.lenovo.lmrp.base.event.EventSource;
import com.lenovo.lmrp.base.event.EventTopic;
import com.lenovo.lmrp.base.map.Address;
import com.lenovo.lmrp.base.map.BaiduMap;
import com.lenovo.lmrp.server.itsmgateway.adapter.AItsmAdapter;
import com.lenovo.lmrp.server.itsmgateway.domain.engineer.IEngineerRepository;
import com.lenovo.lmrp.server.itsmgateway.domain.metadata.ITicketSystemRepository;
import com.lenovo.lmrp.server.itsmgateway.domain.metadata.ItsmEntityTranslator;
import com.lenovo.lmrp.server.itsmgateway.domain.metadata.ItsmEntityTranslator.EntityName;
import com.lenovo.lmrp.server.itsmgateway.domain.ticket.ITicketRepository;
import com.lenovo.lmrp.server.itsmgateway.repository.ARepositoryFactory;
import com.lenovo.sdc.framework.dao.ADaoFactory;
import com.lenovo.sdc.framework.dao.ICommonDAO;
import com.lenovo.sdc.framework.mq.AMQClient;
import com.lenovo.sdc.framework.util.AttributeBean;
import com.lenovo.sdc.framework.util.CommonUtil;
import com.lenovo.sdc.framework.util.EncryptUtil;
import com.lenovo.sdc.framework.util.JSONHelper;
import com.lenovo.sdc.framework.util.Tuple;
import com.lenovo.sdc.framework.util.exception.ErrorCode;
import com.lenovo.sdc.framework.util.exception.TraceableException;
import com.sun.net.httpserver.HttpExchange;

/**
 * 
 * @author wujin
 * @version v1.0
 */
@SuppressWarnings("restriction")
public class DefaultRemedySyncHandler implements IRemedySyncHandler {
    private static final Logger logger = LoggerFactory.getLogger(DefaultRemedySyncHandler.class);

    private String itsmCode;
    private boolean isAsync;

    private ObjectMapper mapper;
    private Map<String, Method> dispatchedMap;

    private ItsmEntityTranslator translator;

    private ThreadPoolExecutor threadPool;

    private IFileService fileHelper;
    private String cdnRootPath;

    private AMQClient mqclient;
    
    private BaiduMap baiduMap;

    /**
     * construct instance with itsm adapter parameter;
     * 
     * @param params
     */
    public DefaultRemedySyncHandler(AttributeBean params) {
        dispatchedMap = new HashMap<>();
        itsmCode = params.getAttribute(AItsmAdapter.ADAPTER_ITSMCOMDE, "xihe");
        isAsync = params.getAttribute("adapter.async", true);

        mapper = new ObjectMapper(new XmlFactory());
        mapper.configure(Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);

        threadPool = new ThreadPoolExecutor(2, Runtime.getRuntime().availableProcessors() * 2, 30, TimeUnit.MINUTES,
            new ArrayBlockingQueue<Runnable>(1000));

        translator = new ItsmEntityTranslator();
        baiduMap = new BaiduMap(params);
        cdnRootPath = params.getAttribute("cdn.path", "");
    }

    public void set(IFileService fileHelper) {
        this.fileHelper = fileHelper;
    }

    public void set(AMQClient mqclient) {
        this.mqclient = mqclient;
    }

    public void processData(String qname, String xmlData, WebServiceContext wsContext) {
        if (Objects.nonNull(wsContext))
            logClientInfo(wsContext);

        processData(qname, xmlData);
    }

    @Override
    public void processData(String qname, String xmlData) {
        if (logger.isDebugEnabled())
            logger.debug("method {}, content {}", qname, xmlData);

        // wrap pushed data and put to thread pool

        Method method = dispatchedMap.get(qname);
        if (Objects.isNull(method)) {
            try {
                synchronized (dispatchedMap) {
                    method = dispatchedMap.get(qname);
                    if (Objects.isNull(method)) {
                        method = this.getClass().getDeclaredMethod("handle" + CommonUtil.toCapitalLetters(qname),
                            LowerCaseMap.class);
                        dispatchedMap.put(qname, method);
                    }
                }
            }
            catch (Exception exp) {
                throw new TraceableException(ErrorCode.COMM_FUNC_NOTFOUND, qname);
            }
        }

        try {
            @SuppressWarnings("unchecked")
            LowerCaseMap<String> event = mapper.readValue(xmlData, LowerCaseMap.class);
            if (isAsync)
                threadPool.submit(new SyncTask(method, this, event));
            else
                new SyncTask(method, this, event).run();
        }
        catch (Exception exp) {
            throw new TraceableException(ErrorCode.COMM_PARSE_FAILED, exp);
        }
    }

    /**
     * convert XiHe ticket data to plateform record then store to DB and send out MQ message to notify other module
     * 
     * @param event
     * @return
     */
    protected void handleTicket(LowerCaseMap<String> event) {
        TicketPO ticketPO = toTicketPO(event);
        ITicketRepository ticketRepository = ARepositoryFactory.getFactory().getTicketRepository();
        TicketPO oldticketPO = ticketRepository.getTicketPO(itsmCode,ticketPO.getId());
        boolean isNew = ticketRepository.storeSyncTicket(itsmCode, ticketPO);
        boolean isReWork = false;
        //Determine whether the ticket is re-push
        if (isNew!=true && oldticketPO !=null){
            // store sync ticket data to DB and update ticketsla set finished = 0 
            String stepname = ticketPO.getOthers().getAttributeBean().getAttribute("stepname", "服务实施");
            String oldstepname = oldticketPO.getOthers().getAttributeBean().getAttribute("stepname", "服务实施");
            if(stepname.equals("服务实施") && !oldstepname.equals("服务实施")){
                ticketPO.setStartTime(new Date());
                if (ticketPO.getPolicy() == 1)
                    ticketPO.setStatus(0);
                else
                    ticketPO.setStatus(1);
                ticketPO.getOthers().getAttributeBean().addAttribute("rework", 1);
                ticketRepository.storeTicketWithExtense(itsmCode,ticketPO);
                ticketRepository.storeTicketSLA(itsmCode,ticketPO.getId());
                isNew = true;
                isReWork = true;
            }
        }       
        // release resource to avoid memory leak
        ticketPO.getTicketSla().clear();
        ticketPO.setTicketSla(null);
        
        ticketPO.getOthers().getAttributeBean().clean();
        ticketPO.setOthers(null);
        
        sendTicketEvent(ticketPO, isNew,isReWork);
    }
    
    /**
     * convert XiHe ticket data to plateform record
     * 
     * @param event
     * @return
     */
    protected TicketPO toTicketPO(LowerCaseMap<String> event) {
        try {
            // find out entity metadata from DB
            ITicketSystemRepository repository = ARepositoryFactory.getFactory().getTicketSystemRepostory();
            EntityMetadataPO entityMetadata = repository.getEntityMetadata(itsmCode, EntityName.TICKET);

            //convert raw data to ticketPO and do some specail convert
            TicketPO ticketPO = translator.translate(entityMetadata, event);
            if (Objects.nonNull(ticketPO.getOthers())) {
                ticketPO.getOthers().setId(ticketPO.getId());
                ticketPO.getOthers().setExtprops(ticketPO.getOthers().getAttributeBean().dump());
            }
            
            if (event.get("isgrab").equals("1"))
                ticketPO.setStatus(0);
            else
                ticketPO.setStatus(1);
            
            List<TicketSLAPO> slaList = new ArrayList<>(3);
            TicketSLAPO sla1 = new TicketSLAPO();
            sla1.setId(EncryptUtil.getUUID());
            sla1.setTicketId(ticketPO.getId());
            sla1.setStep(TicketSLAStep.RESPONSE);
            sla1.setPreWarning(ticketPO.getOthers().getAttributeBean()
                .getAttribute("expect_warningtimerespond", (Date)null));
            sla1.setOvertime(ticketPO.getOthers().getAttributeBean()
                .getAttribute("expect_completetimerespond", (Date)null));
            slaList.add(sla1);

            TicketSLAPO sla2 = new TicketSLAPO();
            sla2.setId(EncryptUtil.getUUID());
            sla2.setTicketId(ticketPO.getId());
            sla2.setStep(TicketSLAStep.VISIT);
            sla2.setPreWarning(ticketPO.getOthers().getAttributeBean()
                .getAttribute("expect_warningtimevisit", (Date)null));
            sla2.setOvertime(ticketPO.getOthers().getAttributeBean()
                .getAttribute("expect_completetimevisit", (Date)null));
            slaList.add(sla2);

            TicketSLAPO sla3 = new TicketSLAPO();
            sla3.setId(EncryptUtil.getUUID());
            sla3.setTicketId(ticketPO.getId());
            sla3.setStep(TicketSLAStep.RESOLVE);
            sla3.setPreWarning(ticketPO.getOthers().getAttributeBean()
                .getAttribute("expect_warningtimeresolve", (Date)null));
            sla3.setOvertime(ticketPO.getOthers().getAttributeBean()
                .getAttribute("expect_completetimeresolve", (Date)null));
            slaList.add(sla3);
            
            ticketPO.setTicketSla(slaList);
            
            obtainAddress(ticketPO);
            
            return ticketPO;
        }
        catch (Exception exp) {
            throw new TraceableException(ErrorCode.COMM_PARSE_FAILED, exp);
        }
    }
    
    private void obtainAddress(TicketPO ticketPO) {
        String caddress = ticketPO.getOthers().getAttributeBean().getAttribute("customeraddress", "");
        if (CommonUtil.isNullOrEmpty(caddress))
            return;
        
        try (ICommonDAO dao = ADaoFactory.getDefault().createDAO(itsmCode)){
            //获取上门地址经纬度信息
            String id = String.valueOf(caddress.hashCode());
            AddressPO address = dao.get(AddressPO.class, id);
            if (Objects.isNull(address)) {
                Address baddress = baiduMap.getLongLatFromAddress(caddress);
                ticketPO.setLatitude(baddress.getLocation().getLat());
                ticketPO.setLongitude(baddress.getLocation().getLng());
                address = new AddressPO();
                address.setId(id);
                address.setAddress(caddress);
                address.setLatitude(ticketPO.getLatitude());
                address.setLongitude(ticketPO.getLongitude());
                
                dao.beginTrans();
                dao.save(address);
                dao.endTrans();
            }
            else {
                ticketPO.setLatitude(address.getLatitude());
                ticketPO.setLongitude(address.getLongitude());
            }
        }
        catch (Exception exp) {
            logger.warn("error when get {} the latitude and longitude for a new ticket {}.", caddress,ticketPO.getId(),exp);
        }
    }
    
    private void sendTicketEvent(TicketPO ticketPO, boolean isNew,boolean isReWork) {
        try {
            // send mq to notify trace
            if (Objects.nonNull(mqclient)) {
                TicketEventPO eventPO = new TicketEventPO();
                eventPO.setItsmCode(itsmCode);
                eventPO.setTicketID(ticketPO.getId());
                eventPO.setEventTime(new Date());
                eventPO.setSource(EventSource.ITSMGW_ADAPTER.name());
                eventPO.setAction(isReWork ? "rework" : isNew ? "create" : "update");
                
                // content don't expose other and sla info
                eventPO.setContent(ticketPO);
                
                EventContent<TicketEventPO> notification = new EventContent<>();
                notification.setSource(EventSource.ITSMGW_ADAPTER);
                notification.setCode(isNew ? EventCode.TICKET_CREATE : EventCode.TICKET_UPDATE);
                notification.setContent(eventPO);
                mqclient.publish(EventTopic.TICKET_EVENT.name(), notification.toJson());
            }
        }
        catch (Exception exp) {
            throw new TraceableException(ErrorCode.MQ_PUBLISH_FAILED, exp);
        }
    }

    protected void handleEngineer(LowerCaseMap<String> event) {
        try {
            //convert raw data to ProjectEngineerPO, ProjectPO, EngineerPO and do some specail convert
            ProjectEngineerPO po = translator.translate(itsmCode, EntityName.PROJECTENGINEER, event);
            po.setSiteAdmin(!po.getSiteAdmin()); //0 - 是， 1 - 否
            po.setStatus(po.getStatus() == 0 ? 1 : 0); // 0 - 有效， 4 - 无效
            po.setRegion((Objects.isNull(po.getEngineer().getProvince()) ? "" : po.getEngineer().getProvince()) + "|" 
                            + (Objects.isNull(po.getEngineer().getCity()) ? "" : po.getEngineer().getCity()) + "|"
                            + (Objects.isNull(po.getEngineer().getCounty()) ? "" : po.getEngineer().getCounty()));
            
            if (CommonUtil.isNullOrEmpty(po.getId()))
                po.setId("PE" + (po.getProjectId() + po.getEngineerId()).hashCode() + po.getRegion().hashCode());
            
            po.getProject().setId(po.getProjectId());
            po.getEngineer().setId(po.getEngineerId());
            po.getEngineer().setLoginName(po.getEngineerId());
            
            // store sync ticket data to DB
            IEngineerRepository repository = ARepositoryFactory.getFactory().getEngineerRepository();
            repository.storeSyncProjectEngineer(itsmCode, po);
        }
        catch (Exception exp) {
            throw new TraceableException(ErrorCode.COMM_PARSE_FAILED, exp);
        }
    }

    protected void handleGroup(LowerCaseMap<String> event) {

    }

    protected void handleBalance(LowerCaseMap<String> event) {

    }

    protected void handleProject(LowerCaseMap<String> event) {
        try {
            //convert raw data to ProjectPO and do some specail convert
            ProjectPO po = translator.translate(itsmCode, EntityName.PROJECT, event);
            if(po.getStatus() == 10)
                po.setStatus(1);
            else if (po.getStatus() == 20)
                po.setStatus(2);
            
            if (Objects.nonNull(po.getOthers())) {
                po.getOthers().setId(po.getId());
                po.getOthers().setExtprops(po.getOthers().getAttributeBean().dump());
            }
            
            // store sync ticket data to DB
            IEngineerRepository repository = ARepositoryFactory.getFactory().getEngineerRepository();
            repository.storeSyncProject(itsmCode, po);
        }
        catch (Exception exp) {
            throw new TraceableException(ErrorCode.COMM_PARSE_FAILED, exp);
        }
    }

    protected void handleSupplier(LowerCaseMap<String> event) {

    }

    protected void handleAttachment(LowerCaseMap<String> event) {
        try {
            String attach = event.remove("attach");
            attach = attach.replaceAll("\\n", "");//羲和提交的文件流base64字符串中含有换行符
            //convert raw data to ProjectEngineerPO, ProjectPO, EngineerPO and do some specail convert
            AttachmentPO po = translator.translate(itsmCode, EntityName.ATTACHMENT, event);
            po.setId(EncryptUtil.getUUID());
            po.setCreateTime(new Date());
            po.setUpdateTime(new Date());
            po.setCdnURL(cdnRootPath + itsmCode + "/" + po.getTicketId() + "/" + po.getId() + "-" + po.getFileName());

            // save attachment file to local stroage or remote FTP server
            Tuple.Pair<String, Long> result = fileHelper.writeFile(itsmCode + "/" + po.getTicketId() + "/" + po.getId()
                                                                   + "-" + po.getFileName(), Base64.getDecoder()
                .decode(attach));

            po.setStorePath(result.getOne());
            po.setStatus(AttachmentStatus.SYNCED);

            ITicketRepository repository = ARepositoryFactory.getFactory().getTicketRepository();
            repository.storeTicketAttachment(itsmCode, po);
        }
        catch (Exception exp) {
            logger.debug("", exp);
        }
    }

    private static class SyncTask implements Runnable {
        private Method method;
        private Object instant;
        private LowerCaseMap<String> event;

        /**
         * @param method
         * @param instant
         * @param xmlData
         */
        public SyncTask(Method method, Object instant, LowerCaseMap<String> event) {
            this.method = method;
            this.instant = instant;
            this.event = event;
        }

        public void run() {
            try {
                method.invoke(instant, event);
            }
            catch (TraceableException exp) {
                
            }
            catch (Exception exp) {
                logger.debug("found error when {}",method.getName());
                try {
                    DefaultRemedySyncHandler handler = (DefaultRemedySyncHandler)instant;
                    EventContent<ExceptionEventContent> content = new EventContent<>();
                    content.setCode(EventCode.EXCEPTION_HANDLEDATA);
                    content.setSource(EventSource.ITSMGW_ADAPTER);
                    ExceptionEventContent eventContent = new ExceptionEventContent();
                    eventContent.setMethod(method.getName());
                    eventContent.setErrorCode(BizErrorCode.ITSMGW_COMM_ERROR.getCode());
                    eventContent.setErrorMessage(exp.getMessage());
                    eventContent.setItsmcode(handler.itsmCode);
                    Map<String, Object> others = new HashMap<>();
                    others.put("ticketid", event.get("ticketid"));
                    others.put("assignee", event.get("assigneefullname"));
                    eventContent.setOthers(others);
                    content.setContent(eventContent);
                    if (Objects.nonNull(handler.mqclient)) {
                        handler.mqclient.publish(EventTopic.EXCEPTION.name(), JSONHelper.toJSON(content));
                    }
                    logger.debug("exception info sended by mq.content is {}",JSONHelper.toJSON(content));
                }
                catch (Exception e) {
                    throw new TraceableException(ErrorCode.MQ_PUBLISH_FAILED, exp.getMessage());
                }
                throw new TraceableException(ErrorCode.COMM_ERROR, exp.getMessage());
            }
        }
    }

    private void logClientInfo(WebServiceContext wsContext) {
        try {
            MessageContext mc = wsContext.getMessageContext();

            HttpExchange exchange = (HttpExchange) mc.get("com.sun.xml.internal.ws.http.exchange");
            String req = (String) mc.get("javax.xml.ws.soap.http.soapaction.uri");
            InetSocketAddress isa = exchange.getRemoteAddress();
            if (logger.isDebugEnabled())
                logger.info("{} {} {}", System.currentTimeMillis(), isa.getAddress().getHostAddress(),
                    req.substring(req.lastIndexOf("/")));
        }
        catch (Exception e) {

        }

    }
}
